# -*- coding: utf-8 -*-

from AE import *
import Attribute
reload( Attribute)
from Attribute import *

import AttributeType
reload( AttributeType)
from AttributeType import *

import Relation
reload( Relation)
from Relation import *

import RelationType
reload( RelationType)
from RelationType import *

import db
reload( db)
from db import McfConnector

import re


class Ontology():
	"""Ontology"""
	def __init__(self, libmyCF, name):
		self.libmyCF = libmyCF
		self.name=name
		self.all_AEs={}
		self.all_relations=[]
		self.all_relations_type={}
		self.all_attributes=[]
		self.all_attributes_type={}
		self.connector = libmyCF.connector
		self.get_all_AEs()
		self.get_all_relations_type()
		self.get_all_relations()
		self.get_all_attributes_type()
		self.get_all_attributes()
		self.set_translation("English translation")

		
        
	def set_translation(self,translation):
		self.translation = translation
	
	def addslashes(self, s):
		"""Add slashes in string before ' for MySQL"""
		return re.sub("(\\\\|'|\")", lambda o: "\\" + o.group(1), s)

	def add_attribute( self,attribute_type_id,AE_id,value):
		"""add an attibute to a given AE"""
		query="INSERT INTO Attributes VALUES (NULL,'"+ str(attribute_type_id)+"','"+ str(AE_id)+"','"+ str(value)+"');"
		self.connector.mcfQueryExec(query)
		# Get the id of the new attribute
		query="SELECT attribute_id FROM Attributes WHERE AE_id = "+str(AE_id)+";"
		attribute_id= int(self.connector.mcfQuery(query)[0][0])
		# Add attribute into the local ontology
		newAtt = Attribute( libmyCF=self.libmyCF, attribute_id=attribute_id, attribute_type_id=attribute_type_id, AE_id=AE_id, value=value)
		self.all_attributes.append( newAtt)
		return newAtt


	def add_attribute_type( self, name, type, definition, example):
		"""Add a new attribute type"""
		query="INSERT INTO Attribute_Type VALUES (NULL,'"+ str(name)+"','"+ str(type)+"','"+ self.addslashes(str(definition))+"','"+ self.addslashes(str(example))+"');"
		self.connector.mcfQueryExec(query)  
		# Get the ID of the new attribute type
		query="SELECT attribute_type_id FROM Attribute_Type WHERE name = '"+ str(name)+"';"
		attribute_type_id= int(self.connector.mcfQuery(query)[0][0])
		# Add attribute type into the local ontology
		newAttType = Attribute_type( self.libmyCF, attribute_type_id, name, type, definition, example)
		self.all_attributes_type[attribute_type_id] = newAttType
		return newAttType


	def add_AE( self, parent,relation_type_id, name , definition="", source="", curator=""):
		"""Add a new AE to the ontology, parent and type of relation with the given parent are required."""
		query="INSERT INTO Anatomical_Entities VALUES (NULL,'"+ name +"', '"+  self.addslashes(definition) +"', '"+  self.addslashes(source)+"', '"+ self.addslashes(curator)+"');"
		print query
		self.connector.mcfQueryExec(query)
		# Get the AE_id of the just created AE
		query = "SELECT AE_id FROM Anatomical_Entities where name='"+  name +"';"
		ae_id= self.connector.mcfQuery(query)[0][0]
		# Connect the new AE to  AE tree (new version) and retrieve the relation_id
		relation_id = self.add_relation(ae_id, parent, relation_type_id).relation_id
		###relation_id = int(self.add_relation(ae_id, parent, relation_type_id))
		# Insert the new AE in the local ontology
		newAE = AE( libmyCF=self.libmyCF, AE_id=int(ae_id), name=name, definition=definition, source=source, curator=curator)
		self.all_AEs[ae_id] = newAE
		return newAE


	def add_relation( self, child, parent, relation_type_id):
		"""Add a relation between 2 AEs"""
		query="INSERT INTO Relations VALUES (NULL,"+ str(child) +","+ str(parent) +","+ str(relation_type_id) +");"
		self.connector.mcfQueryExec(query) 
		# Get the relation_id of the new relation
		query = "SELECT relation_id FROM Relations where child="+ str(child)+" and parent="+ str(parent) +" and relation_type_id="+ str(relation_type_id) +";"
		q = self.connector.mcfQuery(query)
		relation_id= q[0][0]
		# Add the new relation into the local ontology
		newRel = Relation( libmyCF=self.libmyCF, relation_id=relation_id, child=child, parent=parent, retaltion_type_id=relation_type_id)
		self.all_relations.append(newRel)
		return newRel


	def add_relation_type(self, name, definition, example):
		"""Add a new relation type"""
		query="INSERT INTO Relation_Type VALUES (NULL,'"+ str(name)+"','"+ self.addslashes(str(definition)) +"','"+ self.addslashes(str(example))+"');"
		self.connector.mcfQueryExec(query)  
		# Get the ID of the new relation type
		query="SELECT relationtype_id FROM Relation_Type WHERE name = '"+ str(name)+"';"
		relation_type_id= int(self.connector.mcfQuery(query)[0][0])
		# Add relation type into the local ontology
		self.all_relations_type[relation_type_id] = Relation_type( libmyCF=self.libmyCF, relation_type_id=relation_type_id, name=name, definition=definition, example=example)
		return relation_type_id


	def del_relation(self, relation_id):
		"""delete from database the relation with the given ID"""
		#delete relation from remote database
		query='DELETE FROM Relations WHERE relation_id = '+ str(relation_id)+';'
		#print query
		sql= self.connector.mcfQueryExec(query)        

		#delete relation from the local ontology
		for relation in self.all_relations :
			if relation.relation_id == relation_id :
				self.all_relations.remove(relation)
				break


	def del_AE(self, AE_id, *relation_id):
		"""Delete from the database a AE only if it doesn't have child"""
#		count = 0
#		for AE_id in self.all_AEs :
#			#AE_id = AE.AE_id
#			if AE_id == 1 :
#				print("AE 1 non effacé")
#			else :
#				query='DELETE FROM Anatomical_Entities WHERE AE_id = '+ str(AE_id)+';'
#				sql= self.connector.mcfQueryExec(query)
#				count += 1
#		for rel in self.all_relations :
#			rel_id = rel.relation_id
#			query='DELETE FROM Relations WHERE relation_id = '+ str(rel_id)+';'
#			#print query			sql= self.connector.mcfQueryExec(query)
#		print count        
		#delete AE from remote database
		query='DELETE FROM Anatomical_Entities WHERE AE_id = '+ str(AE_id)+';'
		sql= self.connector.mcfQueryExec(query)
		#delete AE from local ontology
		self.all_AEs.pop(AE_id)
		#delete the relation between the AE and his parent from remote DB and from local ontology
		if len(relation_id) == 1 :
			self.del_relation(relation_id[O])
		else :
			for relation in self.all_relations :
				if relation.child == AE_id :
					self.del_relation(relation.relation_id)
		

	def del_attribute(self,attribute_id):
		"""delete the attribute with the given attribute_id"""
		#delete attribute from the remote database
		query = "DELETE FROM Attributes WHERE attribute_id = "+str(attribute_id)+";"
		self.connector.mcfQueryExec(query)
		#delete from the local ontology
		for attribute in self.all_attributes :
			if attribute.attribute_id == attribute_id :
				self.all_attributes.remove(attribute)
				break

# USELESS : when editing the value, the attribute with the old value is removed, and the attribute with the new attribute is created         
# def update_attribute(self,attribute_id,value):
#     """update the value of the attribute with the given id"""
#     #update remote database
#     query = "UPDATE Anatomical_Entities SET value='"+str(value)+"' WHERE attribute_id = "+str(attribute_id)+";"
#     self.connector.mcfQueryExec(query)
#     #update local ontology
#     for attribute in self.all_attributes :
#         if attribute_id == attribute_id :
#             old_attribute = attribute
#             self.all_attributes.remove(old_attribute)
#             new_attribute = Attribute(old_attribute.ontology, attribute_id, old_attribute.attribute_type_id,old_attribute.AE_id, value)
#             self.all_attributes.append(new_attribute)
#             break


	def get_all_AEs(self):
		"""Load Anatomical entities from DataBase """
		#SQL: Load dictionary of Anatomical entity
		query="SELECT * FROM Anatomical_Entities ORDER BY name"
		# print mcfQuery(query)
		for data in self.connector.mcfQuery(query):
			#if int(data[0])==49:
			#	print data
			self.all_AEs[int(data[0])] = AE( libmyCF=self.libmyCF, AE_id=int(data[0]),name=data[1], definition=data[2], source=data[3], curator=data[4])

    
	def get_all_relations(self):
		"""Load all relations between Anatomical entities from DataBase """
		#SQL: Load dictionary of Anatomical entity
		query="SELECT * FROM Relations"
		q = self.connector.mcfQuery(query)
		for data in q:
			self.all_relations.append(Relation( libmyCF=self.libmyCF, relation_id=data[0], child=data[1], parent=data[2], retaltion_type_id=data[3]))


	#def get_l_relations(self):  #now named get_all_relations_type
	def get_all_relations_type(self):
		"""Return a dictionary of relation_type""" 
		for data in self.connector.mcfQuery("SELECT * FROM Relation_Type"):
			self.all_relations_type[int(data[0])] = Relation_type( self.libmyCF, relation_type_id=int(data[0]), name=data[1], definition=data[2], exemple=data[3])


	def get_all_attributes(self):
		"""Load all attributes between Anatomical entities from DataBase """
		#SQL: Load dictionary of Anatomical entity
		query="SELECT * FROM Attributes"
		q = self.connector.mcfQuery(query)
		for data in q:
			self.all_attributes.append( Attribute( libmyCF=self.libmyCF, attribute_id=int(data[0]), attribute_type_id=int(data[1]), AE_id=int(data[2]), value=data[3]))


	def get_all_attributes_type(self): #A modifier ? (utilisé pour l'ajout d'attribut dans dialog edit)
		"""Return a dictionary of attribute_type""" 
		for data in self.connector.mcfQuery("SELECT * FROM Attribute_Type"):
			self.all_attributes_type[int(data[0])] = Attribute_type( self.libmyCF, attribute_type_id=int(data[0]), name=data[1], type=data[2], definition=data[3], example=data[4])      


	def getAENamed( self, name):
		#Should not use a sql request to interrogate the remote database but search the result in the local ontology
		#"""Retrieve the anatomical entity named 'name' from DataBase"""
		ae = []
		query="SELECT * FROM Anatomical_Entities WHERE name = \"%s\""%( name)
		for data in self.libmyCF.connector.mcfQuery(query):
			ae.append( AE( libmyCF=self.libmyCF, AE_id=int(data[0]),name=data[1], definition=data[2]))
		return ae[0]


	def getAEsPartOf( self, entity):
		#"""Retrieve the anatomical entity named 'name' from DataBase"""
		# Get relation type id of 'part_of' entry
		relType = self.getRelationTypeNamed( "part_of")
		relID = relType.relation_type_id
		
		result = []
		stack = entity.get_l_child( relID)
		while stack.__len__() != 0:
			ae = stack.pop()
			result.append( ae)
			for child in ae.get_l_child( relID):
				stack.append( child)
		return result


	def getAttributesNamed( self, name):
		#Should not use a sql request to interrogate the remote database but search the result in the local ontology
		"""Retrieve the attributes named 'name' from DataBase"""
		attributes = []
		#query="SELECT * FROM Attributes AS A INNER JOIN (SELECT * FROM Attribute_Type WHERE name =\"%s\") AS AT ON ( A.attribute_type_id = AT.attribute_type_id)"%(name)
		#for data in self.libmyCF.connector.mcfQuery(query):
		#	attributes.append( Attribute( self.libmyCF, data[0], data[1], data[2], data[3]))
		for att in self.all_attributes :
			if att.getName() == name :
				attributes.append(att)
		return attributes
		


	def getRelationTypeNamed( self, relationTypeName):
		"""Return the relationType"""
		relTypes = self.libmyCF.ontology.all_relations_type
		relID = -1
		for relType in relTypes.values():
			if( relType.name == relationTypeName):
				relID = relType.relation_type_id
		if( relID == -1): print "bad relation type name given"
		return self.all_relations_type[relID]


	#function used when creating a new relation
	def has_parent(self,AE_id,relation_type):
		"""check if the AE identified by its AE_id  has already parent for a given relation type"""
		for relation in self.all_relations :
			if (relation.relation_type_id == relation_type) and (relation.child == AE_id):
				return True
		return False


	def check_db_integrity(self):
		"""check the integrity of the ontology"""
		messages = []
		errors = 0
		
		#verify that an AE has only one parent
		messages.append("CHECKING THAT ALL AEs HAVE ONLY ONE PARENT.\n\n")
		sub_errors = 0
		
		d=dict()
		for relation in self.all_relations:
			rel = (relation.relation_type_id, relation.child,)
			if rel not in d :
				d[rel] = [relation.relation_id]
			else :
				d[rel].append(relation.relation_id)		
		for rel in d :
			k = len(d[rel])
			if k>1:
				sub_errors += k-1
				(rel_type_id,child_id) = rel
				messages.append("   The AE \'%s\' (id=%d) has several parents for the relation type \'%s\' (id=%d) :\n"%(self.all_AEs[child_id].name,self.all_AEs[child_id].AE_id,self.all_relations_type[rel_type_id].name,rel_type_id))
				for rel_id in d[rel] :
					for relation in self.all_relations :
						if rel_id == relation.relation_id :
							messages.append("       - \'%s\' (id=%d)\n"%(self.all_AEs[relation.parent].name,self.all_AEs[relation.parent].AE_id))
				messages.append("\n")				

		if sub_errors == 0:
			messages.append("ALL AEs HAVE ONLY ONE PARENT.\n")
		else :
			messages.append("SOME AEs HAVE MORE THAN ONE PARENT. \n%d RELATIONS NEED TO BE CORRECTED\n"%sub_errors)
		messages.append("_________________________________\n\n")
		errors += sub_errors


		#verify that 2 differents AE do not have the same name
		messages.append("CHECKING THAT ALL AEs HAVE DIFFERENT NAMES...\n\n")
		sub_errors = 0

		d = dict()
		for ae in self.all_AEs:
			name = self.all_AEs[ae].name
			if name not in d :
				d[name] = [ae]
			else :
				d[name].append(ae)
		#print(len(d))
		for name in d:
			if len(d[name])>1 :
				sub_errors += len(d[name])-1
				messages.append("   - The AEs with the IDs = %s have the same name \'%s\'.\n" % (d[name], name))
		
		if sub_errors == 0 :
			messages.append("ALL AEs HAVE DIFFERENT NAMES.\n")
		else :
			messages.append("\n%d AEs NEED TO BE RENAMED. (or deleted: because of their identical names, some of those AEs might not be connected to the mcfTree)\n"%sub_errors)
		messages.append("_________________________________\n\n")
		errors += sub_errors
		 

		#verify that a relation appears only once in the ontology
		messages.append("CHECKING THAT ALL RELATIONS APPEAR ONLY ONCE...\n\n")
		sub_errors = 0

		d=dict()
		for relation in self.all_relations:
			rel = (relation.relation_type_id, relation.child,relation.parent,)
			if rel not in d :
				d[rel] = [relation.relation_id]
			else :
				d[rel].append(relation.relation_id)		
		for rel in d :
			k = len(d[rel])
			if k>1:
				sub_errors += k-1
				#relation_ids = d[rel]
				(rel_type_id,child_id,parent_id) = rel
				messages.append("   Relations with the IDs %s are identical. The relation is:\n" % (d[rel]))
				messages.append("       - child = %s (id = %d)\n"%(self.all_AEs[child_id].name,self.all_AEs[child_id].AE_id))
				messages.append("       - parent = %s (id = %d)\n"%(self.all_AEs[parent_id].name,self.all_AEs[parent_id].AE_id))
				messages.append("       - relation type = %s (id = %d)\n"%(self.all_relations_type[rel_type_id].name,self.all_relations_type[rel_type_id].relation_type_id))						 
				messages.append("\n")
			
		if sub_errors == 0 :
			messages.append("ALL RELATIONS APPEAR ONLY ONCE.\n")
		else :
			messages.append("\nTHERE ARE %d IDENTICAL RELATIONS\n"%(sub_errors*2))
		messages.append("_________________________________\n\n")
		errors += sub_errors

		#building the list of AE_ids that appear in all_relations
		AE_ids = []
		for rel in self.all_relations :
			if rel.child not in AE_ids :
				AE_ids.append(rel.child)
			if rel.parent not in AE_ids :
				AE_ids.append(rel.parent)
				
		#verify that an AE present in he database is connected at least to one other AE
		messages.append("CHECKING THAT ALL AEs ARE CONNECTED TO THE mcfTREE.\n\n")
		sub_errors = 0
#		for k in self.all_AEs :
#			if len(self.all_AEs[k].get_l_relations()) < 1 :
#				messages.append("   The AE \'%s\' (id: %d) is not connected to the mcfTree.\n"%(self.all_AEs[k].name, k))
#				sub_errors += 1
		for AE_id in self.all_AEs :
			if AE_id not in AE_ids :
				messages.append("   The AE \'%s\' (id: %d) is not connected to the mcfTree.\n"%(self.all_AEs[AE_id].name, AE_id))
				sub_errors += 1
        		
		if sub_errors == 0 :
			messages.append("ALL AEs ARE CONNECTED TO THE mcfTREE.\n")
		else :
			messages.append("(\nDisconnected from the mcfTree means that the AE is in relation with no other AE)\n")
			messages.append("%d AEs ARE NOT CONNECTED TO THE mcfTREE.\n"%sub_errors)
		messages.append("_________________________________\n\n")
		errors += sub_errors

		#verify that relations refer to existants AEs :
		messages.append("CHECKING THAT ALL RELATIONS REFER TO EXISTANT AEs...\n\n")
		sub_errors = 0
		
		for k in AE_ids :
			if not k in self.all_AEs :
				for rel in self.all_relations :
					if rel.child == k or rel.parent == k:
						messages.append("   The relation with the ID %d refers to an inexistant AEs (id = %d).\n"%(rel.relation_id,k))
						sub_errors += 1
		if sub_errors == 0 :
			messages.append("ALL RELATIONS REFER TO EXISTANT AEs.\n")
		else :
			messages.append("\n%d RELATIONS REFER TO UNEXISTANT AEs.\n"%sub_errors)
		messages.append("_________________________________\n\n")
		errors += sub_errors

		#verify that attributes refer to existant AEs :
		messages.append("CHECKING THAT ALL ATTRIBUTES REFER TO EXISTANT AEs...\n\n")
		sub_errors = 0
		AE_ids = []
		for att in self.all_attributes :
			AE_ids.append(att.AE_id)
		for k in AE_ids :
			if not k in self.all_AEs :
				for att in self.all_attributes :
					if att.AE_id == k :
						messages.append("   The attribute with the ID %d refers to an inexistant AEs (id = %d).\n"%(rel.relation_id,k))
						sub_errors += 1
		if sub_errors == 0 :
			messages.append("ALL ATTRIBUTES REFER TO EXISTANT AEs.\n")
		else :
			messages.append("\n%d ATTRIBUTES REFER TO UNEXISTANT AEs.\n"%sub_errors)
		messages.append("_________________________________\n\n\n")
		errors += sub_errors


		if errors == 0 :
			summary = "NO ERROR HAS BEEN FOUND IN THE DATABASE.       \n"
			error = False
		else :
			summary = "\n%d ERRORS HAVE BEEN FOUND IN THE DATABASE.     \nCheck the logs for further information."%(errors)
			error = True

		#return the list of messages and the boolean "error" in a tuple
		return (messages, error, summary)
	
	def db_repair(self):
		corrections = 0
		#Delete identical relations
		d=dict()
		for relation in self.all_relations:
			rel = (relation.relation_type_id, relation.child,relation.parent,)
			if rel not in d :
				d[rel] = [relation.relation_id]
			else :
				d[rel].append(relation.relation_id)		
		for rel in d :
			k = len(d[rel])
			if k>1: #i.e. there are many identical relations
				for i in range(k):
					if i != 0: #does not delete the first occurence
						self.del_relation(d[rel][i]) 
						corrections += 1
		#building the list of AE_ids that appear in all_relations
		AE_ids = []
		for rel in self.all_relations :
			if rel.child not in AE_ids :
				AE_ids.append(rel.child)
			if rel.parent not in AE_ids :
				AE_ids.append(rel.parent)
		#delete AE that are not connected to the mcfTree
		to_delete = []
		for AE_id in self.all_AEs :
			if AE_id not in AE_ids :
				to_delete.append(AE_id) #can not delete here, it would modify self.AEs, which is in the current loop
		for AE_id in to_delete : 
			self.del_AE(AE_id)
			corrections += 1
		#delete relations that refer to unexistant AEs
		to_delete = []
		for k in AE_ids :
			if not k in self.all_AEs :
				for rel in self.all_relations :
					if rel.child == k or rel.parent == k:
						to_delete.append(rel.relation_id)
		for rel_id in to_delete :
			self.del_relation(rel_id)
			corrections += 1		
		return corrections


	def check_mycfTree_translation(self,language):
		# return 2 datas : an integer (error_type) and a string (error message)
		# error_type = 0  :  No error
		# error_type = 1  :  Selected translation does not exist
		# error_type = 3  :  All AEs are not translated
		for attribute_type in self.libmyCF.ontology.all_attributes_type :
			existing_language_attribute = False
			if self.libmyCF.ontology.all_attributes_type[attribute_type].name == language :
				existing_language_attribute = True
				break
		if not existing_language_attribute :
			return (1,"You cannot display the ontology in this language.\nYou must create an attribute type named \'%s\' which takes a string for value.\nYou must be in \'write mode\' to do so."%language)
		else :
			language_attribute_type_id = self.libmyCF.ontology.all_attributes_type[attribute_type].attribute_type_id
			missing_translations = 0
			total_AEs = 0
			for AE_id in self.libmyCF.ontology.all_AEs :
				total_AEs += 1
				attributes = self.libmyCF.ontology.all_AEs[AE_id].getOwnAttributes()
				if len(attributes) > 0 :
					existing_language_attribute_for_AE = False
					for att in attributes :
						if att.attribute_type_id == language_attribute_type_id :
							existing_language_attribute_for_AE = True
					if not existing_language_attribute_for_AE :
							missing_translations += 1
				else :
					missing_translations += 1
			if missing_translations > 0 :
				return (2,"Missing %d %s(s) over %d AEs"%(missing_translations,language,total_AEs))
			else : 
				return (0,"No error")